package com.central.scheduler.api.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.central.common.model.Result;
import com.central.common.model.SysUser;
import com.central.common.service.impl.SuperServiceImpl;
import com.central.common.utils.UserRoleUtil;
import com.central.scheduler.api.enums.Status;
import com.central.scheduler.api.service.IScheduleService;
import com.central.scheduler.api.service.IMonitorService;
import com.central.scheduler.common.enums.ReleaseState;
import com.central.scheduler.dao.entity.Server;
import com.central.scheduler.dao.mapper.ProcessDefinitionMapper;
import com.central.scheduler.dao.mapper.ProcessDefinitionOwnersMapper;
import com.central.scheduler.dao.mapper.ScheduleMapper;
import com.central.scheduler.dao.mapper.ScheduleSubProcessMapper;
import com.central.scheduler.dao.model.ProcessDefinition;
import com.central.scheduler.dao.model.ProcessDefinitionOwners;
import com.central.scheduler.dao.model.Schedule;
import com.central.scheduler.dao.model.ScheduleSubProcess;
import com.central.scheduler.service.exception.ServiceException;
import com.central.scheduler.service.process.ProcessService;
import com.central.scheduler.service.quartz.ProcessScheduleJob;
import com.central.scheduler.service.quartz.QuartzExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author Tindy
 * @date 2021/6/24
 * @describe
 */
@Slf4j
@Service
public class ScheduleServiceImpl extends SuperServiceImpl<ScheduleMapper, Schedule> implements IScheduleService {

    @Autowired
    private ProcessDefinitionMapper processDefinitionMapper;
    @Autowired
    private ProcessDefinitionOwnersMapper processDefinitionOwnersMapper;
    @Autowired
    private ProcessService processService;
    @Autowired
    private IMonitorService monitorService;
    @Autowired
    private ScheduleSubProcessMapper scheduleSubProcessMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result createOrUpdate(SysUser user, Schedule schedule) {
        ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(schedule.getProcessDefinitionCode(), user.getCurrentProjectId());
        if (processDefinition != null) {
            //检查用户是否由权限
            if (!checkOperationPermission(user, processDefinition)) return Result.failed("操作失败，用户权限不足");
            //检查定时器是否已上线
            Schedule schedule2 = baseMapper.queryByProcessDefinitionCode(schedule.getProcessDefinitionCode());
            if (schedule2 != null) {
                if (schedule.getReleaseState() == ReleaseState.ONLINE) return Result.failed("定时器已上线不可编辑");
                schedule.setId(schedule2.getId());
            }
            schedule.setReleaseState(ReleaseState.OFFLINE);
            schedule.setUserId(user.getId());
            schedule.setUsername(user.getUsername());
            schedule.setProId(user.getCurrentProjectId());
            schedule.setProName(user.getProName());
            saveOrUpdate(schedule);
            return Result.succeed("操作成功");
        } else return Result.failed("作业流不存在");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result release(SysUser user, Long processDefinitionCode) {
        ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode, user.getCurrentProjectId());
        if (processDefinition != null) {
            //检查定时器是否已上线
            Schedule schedule = baseMapper.queryByProcessDefinitionCode(processDefinitionCode);
            if (schedule == null) return Result.failed("操作失败，定时器不存在");
            // check master server exists
            List<Server> masterServers = monitorService.getServerListFromRegistry(true);

            if (masterServers.isEmpty()) {
                return Result.failed(Status.MASTER_NOT_EXISTS.getMsg());
            }
            if (schedule.getReleaseState() == ReleaseState.ONLINE) {
                schedule.setReleaseState(ReleaseState.OFFLINE);
                scheduleSubProcessMapper.delete(new QueryWrapper<ScheduleSubProcess>().eq("schedule_id", schedule.getId()));
            } else {
                // check process definition release state
                if (processDefinition.getReleaseState() != ReleaseState.ONLINE) {
                    return Result.failed("操作失败，当前作业流未上线");
                }
                // check sub process definition release state
                Set<Long> subProcessDefineCodes = new HashSet<>();
                processService.recurseFindSubProcessCode(processDefinition.getCode(), subProcessDefineCodes);
                if (!subProcessDefineCodes.isEmpty()) {
                    List<ProcessDefinition> subProcessDefinitionList =
                            processDefinitionMapper.queryDefinitionListByCodeSet(subProcessDefineCodes);
                    if (subProcessDefinitionList != null && !subProcessDefinitionList.isEmpty()) {
                        for (ProcessDefinition subProcessDefinition : subProcessDefinitionList) {
                            /**
                             * if there is no online process, exit directly
                             */
                            if (subProcessDefinition.getReleaseState() != ReleaseState.ONLINE) {
                                return Result.failed(MessageFormat.format("操作失败，子作业流 {0} 未上线", processDefinition.getName()));
                            }
                        }
                    }
                }
                if (!subProcessDefineCodes.isEmpty()) {
                    scheduleSubProcessMapper.batchInsert(schedule.getId(), processDefinition.getCode(), subProcessDefineCodes);
                }
                schedule.setReleaseState(ReleaseState.ONLINE);
            }
            schedule.setUserId(user.getId());
            schedule.setUsername(user.getUsername());
            schedule.setProId(user.getCurrentProjectId());
            schedule.setProName(user.getProName());
            saveOrUpdate(schedule);

            switch (schedule.getReleaseState()) {
                case ONLINE:
                    log.info("Call master client set schedule online, project id: {}, flow id: {},host: {}", user.getCurrentProjectId(), processDefinition.getId(), masterServers);
                    setSchedule(user.getCurrentProjectId(), schedule);
                    break;
                case OFFLINE:
                    log.info("Call master client set schedule offline, project id: {}, flow id: {},host: {}", user.getCurrentProjectId(), processDefinition.getId(), masterServers);
                    deleteSchedule(user.getCurrentProjectId(), schedule.getId());
                    break;
                default:
                    throw new RuntimeException(MessageFormat.format(Status.SCHEDULE_STATUS_UNKNOWN.getMsg(), schedule.getReleaseState().toString()));
            }
            return Result.succeed("操作成功");
        } else return Result.failed("操作失败，作业流不存在");
    }

    public void setSchedule(Long proId, Schedule schedule) {
        log.info("set schedule, project id: {}, scheduleId: {}", proId, schedule.getId());

        QuartzExecutors.getInstance().addJob(ProcessScheduleJob.class, proId, schedule);
    }

    /**
     * delete schedule
     *
     * @param proId      project id
     * @param scheduleId schedule id
     * @throws RuntimeException runtime exception
     */
    @Override
    public void deleteSchedule(Long proId, Long scheduleId) {
        log.info("delete schedules of project id:{}, schedule id:{}", proId, scheduleId);

        String jobName = QuartzExecutors.buildJobName(scheduleId);
        String jobGroupName = QuartzExecutors.buildJobGroupName(proId);

        if (!QuartzExecutors.getInstance().deleteJob(jobName, jobGroupName)) {
            log.warn("set offline failure:projectId:{},scheduleId:{}", proId, scheduleId);
            throw new ServiceException("set offline failure");
        }

    }

    @Override
    public Schedule querySchedule(SysUser user, Long processDefinitionCode) {
        return getOne(new QueryWrapper<Schedule>().eq("process_definition_code",processDefinitionCode));
    }

    /**
     * 只有项目管理员和作业流负责人拥有修改权限
     *
     * @param user
     * @param processDefinition
     * @return
     */
    private boolean checkOperationPermission(SysUser user, ProcessDefinition processDefinition) {
        if (user.getCurrentProjectId().equals(processDefinition.getProId()) && UserRoleUtil.checkProAdmin(user.getProRoles()))
            return true;
        ProcessDefinitionOwners owners = processDefinitionOwnersMapper.selectOne(new QueryWrapper<ProcessDefinitionOwners>().eq("process_definition_id", processDefinition.getId()));
        if (owners != null && owners.getOwners().contains(user.getUsername())) return true;
        return false;
    }
}
